/* eslint-disable @typescript-eslint/no-unsafe-function-type */
type ISocketConfig = {
  protocols?: string | string[];
  autoReconnect?:
    | boolean
    | {
        max_retry_time: number;
        retry_interval: number;
      };
  heartbeat?:
    | boolean
    | {
        /**
         * Message for the heartbeat
         *
         * @default 'ping'
         */
        message?: string;
        /**
         * Interval, in milliseconds
         *
         * @default 1000
         */
        interval?: number;
      };
};

type ISocketEvents =
  | 'message'
  | 'error'
  | 'reconnecting'
  | 'reconnectFail'
  | 'open'
  | 'connecting'
  | 'closing'
  | 'closed';

interface ISocketInterface {
  on(event: 'message', handle: (msg: string) => void): void;
  on(event: 'error', handle: (msg?: string) => void): void;
  on(event: 'reconnecting', handle: (retryTime: number) => void): void;
  on(event: 'reconnectFail', handle: () => void): void;
  on(event: 'open', handle: () => void): void;
  on(event: 'connecting', handle: () => void): void;
  on(event: 'closing', handle: () => void): void;
  on(event: 'closed', handle: () => void): void;
}

class ISocket implements ISocketInterface {
  private url: string;
  private socket?: WebSocket;
  private retry_time = 0;
  private reconnectTimer?: ReturnType<typeof setTimeout>;
  private heartTimer?: ReturnType<typeof setTimeout>;
  private eventMap: Map<string, Function[]> = new Map();
  private configuration?: ISocketConfig;
  constructor(url: string, configuration?: ISocketConfig) {
    this.url = url;
    this.configuration = configuration;
    this.open();
  }

  public on(event: ISocketEvents, callback: Function) {
    const callbacks = this.eventMap.get(event) || [];
    callbacks.push(callback);
    this.eventMap.set(event, callbacks);
  }

  public off(event: ISocketEvents, callback: Function) {
    const callbacks = this.eventMap.get(event) || [];
    const index = callbacks.indexOf(callback);
    if (index !== -1) {
      callbacks.splice(index, 1);
    }
    this.eventMap.set(event, callbacks);
  }

  private trigger(event: ISocketEvents, ...args: unknown[]) {
    const callbacks = this.eventMap.get(event) || [];
    callbacks.forEach((callback) => {
      callback(...args);
    });
  }

  private get autoReconnect() {
    const autoReconnect = this.configuration?.autoReconnect;
    if (typeof autoReconnect === 'boolean') return autoReconnect;
    return true;
  }

  private get autoReconnectConfig() {
    let max_retry_time = Infinity;
    let retry_interval = 1000;
    if (typeof this.configuration?.autoReconnect === 'object') {
      max_retry_time = this.configuration?.autoReconnect.max_retry_time;
      retry_interval = this.configuration?.autoReconnect.retry_interval;
    }
    return {
      max_retry_time,
      retry_interval
    };
  }

  private reconnect() {
    if (this.retry_time == this.autoReconnectConfig.max_retry_time) {
      this.socket = undefined;
      this.trigger('reconnectFail');
    } else {
      this.trigger('reconnecting', this.retry_time);
      this.reconnectTimer = setTimeout(() => {
        this.connect();
      }, this.autoReconnectConfig.retry_interval);
    }
  }

  private connect() {
    const source = new WebSocket(this.url, this.configuration?.protocols);

    source.onmessage = ({ data }) => {
      this.trigger('message', data);
    };

    source.onopen = () => {
      this.retry_time = 0;
      this.socket = source;
      this.trigger('open');
      this.initHeartbeat();
    };

    source.onerror = () => {
      if (source.readyState == 3) {
        if (!this.autoReconnect) {
          this.socket = undefined;
          this.trigger('error');
        } else {
          this.retry_time++;
          this.reconnect();
        }
      } else {
        this.trigger('error');
      }
    };

    source.onclose = () => {
      if (this.retry_time === this.autoReconnectConfig.max_retry_time) {
        // 达到最大重连次数
        this.trigger('error', '重连失败,已达最大重连次数');
      } else if (this.socket) {
        this.trigger('closed');
      }
      this.socket = undefined;
    };
  }

  private initHeartbeat() {
    const heartbeat = this.configuration?.heartbeat;
    if (typeof heartbeat === 'boolean') {
      if (!heartbeat) return;
      this.heartTimer = setInterval(() => {
        this.send('ping');
      }, 1000);
    } else if (typeof heartbeat === 'object') {
      const interval = heartbeat.interval || 1000;
      const message = heartbeat.message || 'ping';
      this.heartTimer = setInterval(() => {
        this.send(message);
      }, interval);
    }
  }

  open() {
    if (this.socket) {
      /**
       * socket readyState
       * 0 ：对应常量CONNECTING (numeric value 0)，正在建立连接连接，还没有完成。The connection has not yet been established.
       * 1 ：对应常量OPEN (numeric value 1)，连接成功建立，可以进行通信。The WebSocket connection is established and communication is possible.
       * 2 ：对应常量CLOSING (numeric value 2)连接正在进行关闭握手，即将关闭。The connection is going through the closing handshake.
       * 3 : 对应常量CLOSED (numeric value 3)连接已经关闭或者根本没有建立。The connection has been closed or could not be opened.
       * */
      switch (this.socket.readyState) {
        case 0:
          // 正在连接
          this.trigger('connecting');
          return;
        case 1:
          // 已经连接
          this.trigger('message', '链接成功, 无需重连');
          return;
        case 2:
          // 正在关闭
          this.trigger('closing');
          return;
      }
    }
    this.retry_time = 0;
    this.socket = undefined;
    this.connect();
  }

  public close() {
    this.socket?.close();
    if (this.heartTimer) clearInterval(this.heartTimer);
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer);
      this.trigger('closed');
      return;
    }
  }

  public send(msg: unknown) {
    if (this.socket) {
      this.socket.send(JSON.stringify(msg));
    } else {
      this.trigger('error', '链接已经断开了');
    }
  }
}

export default ISocket;
